package com.sha.kamel.multitogglebutton;

import com.annimon.stream.Stream;
import com.annimon.stream.function.IntConsumer;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.global.resource.Element;
import ohos.global.resource.ResourceManager;

import java.util.ArrayList;
import java.util.List;

public abstract class ToggleButton extends DirectionalLayout implements Defaults {

    private static final String KEY_BUTTON_STATES = "button_states";
    private static final String KEY_INSTANCE_STATE = "instance_state";

    /** A list of rendered items. Used to get state, among others */
    protected List<Text> items;

    /** The specified labels */
    private CharSequence[] labels;
    /** If true, multiple items can be pressed at the same time */
    protected boolean multipleChoice = false;

    /** The layout containing all items */
    protected ComponentContainer rootView;

    protected IntConsumer maxCallback;
    protected int maxItemsToSelect;

    public interface OnItemSelectedListener {
        void onSelected(
                ToggleButton toggleButton,
                Component item,
                int position,
                String label,
                boolean selected);
    }

    private OnItemSelectedListener listener;

    protected float cornerRadius;
    protected int rootViewBackgroundColor;
    protected boolean isCornersRounded;
    protected boolean scrollable;
    protected boolean selectFirstItem;
    protected boolean textAllCaps;

    int colorPressed;

    int colorUnpressed;

    int colorPressedText;

    int colorUnpressedText;

    public ToggleButton(Context context) {
        super(context, null);
    }

    public ToggleButton(Context context, AttrSet attrs) {
        super(context, attrs);
        resolveAttrs(attrs);
    }

    private void resolveAttrs(AttrSet attrs) {
        colorPressed = color(attrs, "mtbPressedColor", Color.BLUE.getValue());
        try {
            Element element = getResourceManager().getElement(ResourceTable.Color_white_unpressed);
            int color = element.getColor();
            colorUnpressed = color(attrs, "mtbUnpressedColor", color);
            rootViewBackgroundColor = colorPressed;
        } catch (Exception e) {
            e.printStackTrace();
        }
        colorPressedText = color(attrs, "mtbColorPressedText");
        colorUnpressedText = color(attrs, "mtbColorUnpressedText");

        isCornersRounded = AttrUtils.getBooleanFromAttr(attrs, "mtbRoundedCorners", false);
        multipleChoice = AttrUtils.getBooleanFromAttr(attrs, "mtbMultipleChoice", false);
        scrollable = AttrUtils.getBooleanFromAttr(attrs, "mtbScrollable", false);
        textAllCaps = AttrUtils.getBooleanFromAttr(attrs, "mtbTextAllCaps", true);
        selectFirstItem = AttrUtils.getBooleanFromAttr(attrs, "mtbSelectFirstItem", true);
        cornerRadius = AttrUtils.getDimensionFromAttr(attrs, "mtbCornerRadius", -1);
    }

    protected void prepare() {
        setOrientation(DirectionalLayout.HORIZONTAL);
        // setGravity(Gravity.CENTER_VERTICAL);

        LayoutScatter inflater = layoutInflater();
        if (rootView == null) {
            int res;
            if (scrollable) {
                LogUtil.loge("prepare=============>" + hasRoundedCorners());
                res =
                        !hasRoundedCorners()
                                ? ResourceTable.Layout_view_root_srcollable
                                : ResourceTable.Layout_view_root_rounded_scrollable;
            } else {
                res =
                        !hasRoundedCorners()
                                ? ResourceTable.Layout_view_root
                                : ResourceTable.Layout_view_root_rounded;
            }
            ComponentContainer v = (ComponentContainer) inflater.parse(res, this, true);
            rootView =
                    scrollable
                            ? (ComponentContainer) v.findComponentById(ResourceTable.Id_rootView)
                            : v;
            ShapeElement shapeElement = new ShapeElement();
            rootView.setBackground(shapeElement);

            if (hasRoundedCorners()) {
                radius(rootView, cornerRadius);
            }
            setBackground(rootView, colorUnpressed);
        }
    }

    private enum ItemPosition {
        LEFT,
        RIGHT,
        INNER,
        SINGLE
    }

    protected Text createTextView(int i, int itemsCount) {
        Text tv = new Button(getContext());
        int padding = (int) SystemUtils.vp2px(getContext(), 19);
        tv.setPadding(padding, 0, padding, 0);
        ResourceManager resourceManager = getResourceManager();
        try {
            int minHeight =
                    (int)
                            resourceManager
                                    .getElement(ResourceTable.Float_button_min_height)
                                    .getFloat();
            int minWidth =
                    (int)
                            resourceManager
                                    .getElement(ResourceTable.Float_button_min_width)
                                    .getFloat();
            tv.setMinHeight(minHeight);
            tv.setMinWidth(minWidth);
        } catch (Exception e) {
            e.printStackTrace();
        }
        tv.setTextSize(14, Text.TextSizeType.FP);

        if (i == 0 && itemsCount != 2) {
            if (itemsCount == 1) {
                tv.setTag(ItemPosition.SINGLE);
                if (hasRoundedCorners()) radius(tv, cornerRadius);
            } else {
                tv.setTag(ItemPosition.LEFT);
                if (hasRoundedCorners()) leftRadius(tv, cornerRadius);
            }
        } else if (itemsCount == 2) {
            tv.setTag(ItemPosition.INNER);
            if (hasRoundedCorners()) radius(tv, cornerRadius);
        } else if (i == itemsCount - 1 && hasRoundedCorners()) {
            tv.setTag(ItemPosition.RIGHT);
            rightRadius(tv, cornerRadius);
        }

        return tv;
    }

    /**
     * Listen to selection states of items
     *
     * @param listener called if state changed
     * @return this
     */
    public ToggleButton setOnItemSelectedListener(OnItemSelectedListener listener) {
        this.listener = listener;
        return this;
    }

    /**
     * an array of items selection
     *
     * @return items selection array
     */
    public boolean[] getSelectionArray() {
        if (isEmpty(items)) return new boolean[] {};
        boolean[] list = new boolean[items.size()];
        for (int i = 0; i < items.size(); i++) list[i] = items.get(i).isSelected();
        return list;
    }

    /**
     * list of items selection
     *
     * @return items selection list
     */
    public List<Boolean> getSelectionList() {
        if (isEmpty(items)) return new ArrayList<>();
        return Stream.of(items).map(Component::isSelected).toList();
    }

    /**
     * list of items selection
     *
     * @return items size
     */
    public int getSelectedItemsSize() {
        return Stream.of(items).filter(Component::isSelected).toList().size();
    }

    /**
     * Set items selection. selection array must be equal to items size
     *
     * @param selected selection array
     * @return this
     */
    public ToggleButton setItemsSelection(boolean[] selected) {
        if (isEmpty(items) || isEmpty(selected) || items.size() != selected.length) return this;
        Stream.of(items).forEachIndexed((i, v) -> setItemSelected(v, selected[i]));
        return this;
    }

    /**
     * Set an item selected
     *
     * @param v item view
     * @param selected true for selected
     * @return this
     */
    public ToggleButton setItemSelected(Component v, boolean selected) {
        if (v == null) return this;

        v.setSelected(selected);
        setBackground(v, selected ? colorPressed : colorUnpressed);

        textStyle(v, selected);
        return this;
    }

    private void textStyle(Component v, boolean selected) {
        Text tv = (Text) v;
        tv.setFont(Font.DEFAULT_BOLD);

        if (isValidColor(colorPressedText) || isValidColor(colorUnpressedText)) {
            int color = selected ? colorPressedText : colorUnpressedText;
            tv.setTextColor(new Color(color));

        } else {
            int color = !selected ? colorPressed : colorUnpressed;
            tv.setTextColor(new Color(color));
        }
    }

    /**
     * a object with zero or more selected items
     *
     * @return {@link Selected}
     */
    public Selected getSelected() {
        List<Text> selected =
                Stream.of(items).filterIndexed((i, item) -> item.isSelected()).toList();

        List<Integer> selectedPositions =
                Stream.of(items)
                        .filterIndexed((i, item) -> item.isSelected())
                        .map(items::indexOf)
                        .toList();
        return new Selected(selected, selectedPositions, items.size());
    }

    /**
     * Reverse selection of an item
     *
     * @param position index of item
     * @return this
     */
    public ToggleButton toggleItemSelection(int position) {
        Text item = items.get(position);
        boolean currentState = item.isSelected();
        Stream.of(items)
                .forEachIndexed(
                        (i, v) -> {
                            // Update selected item only in multiple choice
                            if (multipleChoice) {
                                if (i == position && v != null) setItemSelected(v, !v.isSelected());
                                return;
                            }
                            setItemSelected(items.get(i), i == position);
                        });

        CharSequence label = labels[position];
        notifyItemSelected(item, currentState, position, label.toString());

        return this;
    }

    protected void notifyItemSelected(Text item, boolean oldState, int position, String label) {
        if (listener != null && oldState != item.isSelected())
            listener.onSelected(this, item, position, label, item.isSelected());
    }

    /** update items */
    public void refresh() {
        Stream.of(getSelectionList())
                .forEachIndexed(
                        (i, selected) -> {
                            Component item = items.get(i);
                            refreshView(item);
                            setItemSelected(item, selected);
                        });
        if (hasRoundedCorners()) radius(rootView, cornerRadius);
    }

    private void refreshView(Component item) {
        Object tag = item.getTag();
        if (!hasRoundedCorners() || !(tag instanceof ItemPosition)) return;
        ItemPosition position = (ItemPosition) tag;
        switch (position) {
            case LEFT:
                leftRadius(item, cornerRadius);
                break;

            case RIGHT:
                rightRadius(item, cornerRadius);
                break;

            case SINGLE:
            case INNER:
                radius(item, cornerRadius);
                break;
        }
    }

    /**
     * The desired color resource identifier generated by the aapt tool
     *
     * @param colorPressed color resource ID for the pressed item
     * @param colorUnpressed color resource ID for the released item
     * @return this
     */
    public ToggleButton setColorRes(int colorPressed, int colorUnpressed) {
        setColors(color(colorPressed), color(colorUnpressed));
        return this;
    }

    /**
     * Color values are in the form 0xAARRGGBB
     *
     * @param colorPressed resolved color for the pressed item
     * @param colorUnpressed resolved color for the released item
     * @return this
     */
    public ToggleButton setColors(int colorPressed, int colorUnpressed) {
        try {
            this.colorPressed = colorPressed;
            this.colorUnpressed = colorUnpressed;
            rootViewBackgroundColor = colorUnpressed;
            return this;
        } finally {
            refresh();
        }
    }

    /**
     * The desired color resource identifier generated by the aapt tool
     *
     * @param colorPressedText color resource ID for the pressed createTextView's text
     * @param colorPressedBackground color resource ID for the pressed createTextView's background
     * @return this
     */
    public ToggleButton setPressedColorsRes(int colorPressedText, int colorPressedBackground) {
        setPressedColors(color(colorPressedText), color(colorPressedBackground));
        return this;
    }

    /**
     * The desired color resource for pressed color text
     *
     * @param color resource
     * @return this
     */
    public ToggleButton setPressedColorTextRes(int color) {
        this.colorPressedText = color(color);
        return this;
    }

    /**
     * The desired color for pressed color text
     *
     * @param color resource
     * @return this
     */
    public ToggleButton setPressedColorText(int color) {
        this.colorPressedText = color;
        refresh();
        return this;
    }

    /**
     * The desired color for unpressed color text
     *
     * @param color resource
     * @return this
     */
    public ToggleButton setUnpressedColorTextRes(int color) {
        this.colorUnpressedText = color(color);
        refresh();
        return this;
    }

    /**
     * The desired color for pressed color text
     *
     * @param color resource
     * @return this
     */
    public ToggleButton setUnpressedColorText(int color) {
        this.colorUnpressedText = color;
        refresh();
        return this;
    }

    /**
     * Color values are in the form 0xAARRGGBB
     *
     * @param colorPressedText resolved color for the pressed createTextView's text
     * @param colorPressedBackground resolved color for the pressed createTextView's background
     * @return this
     */
    public ToggleButton setPressedColors(int colorPressedText, int colorPressedBackground) {
        this.colorPressedText = colorPressedText;
        this.colorPressed = colorPressedBackground;
        refresh();
        return this;
    }

    /**
     * The desired color resource identifier generated by the aapt tool
     *
     * @param colorUnpressedText color resource ID for the released createTextView's text
     * @param colorUnpressedBackground color resource ID for the released createTextView's
     *     background
     * @return this
     */
    public ToggleButton setUnpressedColorRes(int colorUnpressedText, int colorUnpressedBackground) {
        setUnpressedColors(color(colorUnpressedText), color(colorUnpressedBackground));
        return this;
    }

    /**
     * Color values are in the form 0xAARRGGBB
     *
     * @param colorNotPressedText resolved color for the released createTextView's text
     * @param colorUnpressedBackground resolved color for the released createTextView's background
     * @return this
     */
    public ToggleButton setUnpressedColors(int colorNotPressedText, int colorUnpressedBackground) {
        this.colorUnpressedText = colorNotPressedText;
        this.colorUnpressed = colorUnpressedBackground;
        rootViewBackgroundColor = colorUnpressed;
        refresh();
        return this;
    }

    /**
     * The desired color resource identifier generated by the aapt tool
     *
     * @param colorPressedText drawable resource ID for the pressed createTextView's background
     * @param colorUnpressedText drawable resource ID for the released createTextView's background
     * @return this
     */
    public ToggleButton setForegroundColorsRes(int colorPressedText, int colorUnpressedText) {
        setForegroundColors(color(colorPressedText), color(colorUnpressedText));
        return this;
    }

    /**
     * Color values are in the form 0xAARRGGBB
     *
     * @param colorPressedText resolved color for the pressed createTextView's text
     * @param colorUnpressedText resolved color for the released createTextView's text
     * @return this
     */
    public ToggleButton setForegroundColors(int colorPressedText, int colorUnpressedText) {
        this.colorPressedText = colorPressedText;
        this.colorUnpressedText = colorUnpressedText;
        refresh();
        return this;
    }

    /**
     * If multiple choice is enabled, the user can select multiple values simultaneously.
     *
     * @param enable true to be multiple selected
     * @return this
     */
    public ToggleButton multipleChoice(boolean enable) {
        this.multipleChoice = enable;
        return this;
    }

    /**
     * Select first item. The first item is selected by default
     *
     * @param selected false to deselect
     * @return this
     */
    public ToggleButton selectFirstItem(boolean selected) {
        this.selectFirstItem = selected;

        if (isEmpty(items)) return this;

        List<Boolean> states = getSelectionList();
        states.set(0, true);
        Stream.of(states).forEachIndexed((i, s) -> setItemSelected(items.get(i), s));
        return this;
    }

    /**
     * An array of the items' labels
     *
     * @return labels
     */
    public CharSequence[] getLabels() {
        return labels == null ? null : labels.clone();
    }

    /**
     * An array of the items' labels
     * @param labels labels
     */
    public void setLabels(CharSequence[] labels) {
        this.labels = labels == null ? null : labels.clone();
    }

    /**
     * Set item label for item by position
     *
     * @param label text
     * @param position index of item
     * @return this
     */
    public ToggleButton setLabel(CharSequence label, int position) {
        setFormatText(items.get(position), label.toString(), textAllCaps);
        return this;
    }

    /**
     * Set item label for item by position
     *
     * @param label text resource
     * @param position index of item
     * @return this
     */
    public ToggleButton setLabel(int label, int position) {
        Text item = items.get(position);
        setFormatText(item, label, textAllCaps);
        return setLabel(getContext().getString(label), position);
    }

    /**
     * Set item label for item by position
     *
     * @param labels texts resources
     * @return this
     */
    public ToggleButton setLabelsRes(List<Integer> labels) {
        List<String> l =
                Stream.of(labels)
                        .map(
                                label -> {
                                    try {
                                        return getResourceManager().getElement(label).getString();
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    }
                                    return "";
                                })
                        .toList();
        return setLabels(l);
    }

    /**
     * Set item label for item by position
     *
     * @param labels texts strings
     * @throws IllegalArgumentException Labels size may not equal to items size.
     * @return this
     */
    public ToggleButton setLabels(List<String> labels) {
        if (isEmpty(items)) return this;
        if (count(labels) != count(items))
            throw new IllegalArgumentException("Labels size may not equal to items size.");

        Stream.of(items)
                .forEachIndexed((i, item) -> setFormatText(item, labels.get(i), textAllCaps));
        return this;
    }

    /**
     * Specify the radius of corners
     *
     * @param cornerRadius radius size
     * @return this
     */
    public ToggleButton setCornerRadius(float cornerRadius) {
        this.cornerRadius = cornerRadius;
        refresh();
        return this;
    }

    /**
     * true if mtbRoundedCorners is set to true or mtbCornerRadius value is greater than zero
     *
     * @return true or false
     */
    public boolean hasRoundedCorners() {
        return cornerRadius != -1 || isCornersRounded;
    }

    /**
     * Return item by position
     *
     * @param position index of item
     * @return item with the specified position
     */
    public Text itemAt(int position) {
        return items.get(position);
    }

    /**
     * list of all items. If no items, an empty array is returned. Never return null
     *
     * @return items list or text
     */
    public List<Text> items() {
        return items == null ? new ArrayList<>() : items;
    }

    /**
     * Specify maximum number of items to be selected
     *
     * @param max number of items
     * @param callbackIfExceeded will be called if the selected items exceeds max
     * @throws IllegalArgumentException max may not be greater than added items
     * @return this
     */
    public ToggleButton maxSelectedItems(int max, IntConsumer callbackIfExceeded) {
        if (max > items.size())
            throw new IllegalArgumentException("max may not be greater than added items");
        maxItemsToSelect = max;
        maxCallback = callbackIfExceeded;
        multipleChoice = true;
        return this;
    }

    /**
     * multiple choice enabled
     *
     * @return true or false
     */
    public boolean isMultipleChoice() {
        return multipleChoice;
    }

    /**
     * root view of all items
     *
     * @return root view
     */
    public ComponentContainer getRootView() {
        return rootView;
    }

    /**
     * max items to select
     *
     * @return max Select size
     */
    public int getMaxItemsToSelect() {
        return maxItemsToSelect;
    }

    public float getCornerRadius() {
        return cornerRadius;
    }

    /**
     * if can scroll
     *
     * @return true or false
     */
    public boolean isScrollable() {
        return scrollable;
    }

    /**
     * if first item is selected by default
     *
     * @return true or false
     */
    public boolean isSelectFirstItem() {
        return selectFirstItem;
    }

    /**
     * if all caps enabled
     *
     * @return true or false
     */
    public boolean isTextAllCaps() {
        return textAllCaps;
    }

    /**
     * color of pressed item
     *
     * @return color id
     */
    public int getColorPressed() {
        return colorPressed;
    }

    /**
     * color of unpressed item
     *
     * @return color id
     */
    public int getColorUnpressed() {
        return colorUnpressed;
    }

    /**
     * text color of pressed text
     *
     * @return color id
     */
    public int getColorPressedText() {
        return colorPressedText;
    }

    /**
     * text color of unpressed text
     *
     * @return color id
     */
    public int getColorUnpressedText() {
        return colorUnpressedText;
    }

    /**
     * Set set rounded corners radius of 18dp by passing true. The default is false
     *
     * @param rounded true if enabled
     * @return this
     */
    public ToggleButton setRoundedCorners(boolean rounded) {
        isCornersRounded = rounded;
        refresh();
        return this;
    }
}
